﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace TrafficTrouble.Core
{
    public class Car
    {
        public Grid ParentGrid { get; set; }
        public byte ID { get; private set; }
        /// <summary>Gets the length of this car, in grid spaces.</summary>
        public int Length { get; private set; }
        /// <summary>Gets whether or not this car is aligned vertically instead of horizontally. Vertical == !Horizontal;</summary>
        public bool Vertical { get; private set; }
        /// <summary>Gets whether or not this car is aligned horizontally instead of vertically. Horizontal == !Vertical.</summary>
        public bool Horizontal
        {
            get { return !this.Vertical; }
            private set { this.Vertical = !value; }
        }

        private int _x, _y;
        /// <summary>Gets or sets the X-coordinate of the left or top of the car.</summary>
        public int X
        {
            get { return _x; }
            set
            {
                _x = value;
                if (_x < 0)
                    _x = 0;
                if (_x > ParentGrid.Size - (this.Horizontal ? this.Length : 1))
                    _x = ParentGrid.Size - (this.Horizontal ? this.Length : 1);
            }
        }
        /// <summary>Gets or sets the Y-coordinate of the left or top of the car.</summary>
        public int Y
        {
            get { return _y; }
            set
            {
                _y = value;
                if (_y < 0)
                    _y = 0;
                if (_y > ParentGrid.Size - (this.Vertical ? this.Length : 1))
                    _y = ParentGrid.Size - (this.Vertical ? this.Length : 1);
            }
        }

        /// <summary>Gets or sets the grid coordinates of the top-left of the car.</summary>
        public Point GridCoords
        {
            get { return new Point(this.X, this.Y); }
            set { this.X = value.X; this.Y = value.Y; }
        }

        /// <summary>Gets whether or not this car is a car that the player is helping to escape.</summary>
        public bool PlayerCar { get; private set; }

        public Car(Grid parentGrid, byte id, int length, bool vertical, int x, int y)
            : this(parentGrid, id, length, vertical, x, y, false) { }
        public Car(Grid parentGrid, byte id, int length, bool vertical, int x, int y, bool playerCar)
        {
            this.ParentGrid = parentGrid;
            this.ID = id;
            this.Length = length;
            this.Vertical = vertical;
            this.X = x;
            this.Y = y;
            this.PlayerCar = playerCar;
        }

        public bool ContainsLocation(int x, int y)
        {
            if (this.Vertical)
                return this.X == x && (this.Y <= y && this.Y + this.Length > y);
            else
                return this.Y == y && (this.X <= x && this.X + this.Length > x);
        }
        public Point[] GetContainingLocations()
        {
            List<Point> locs = new List<Point>();

            int pos = this.Horizontal ? this.X : this.Y;
            while (pos < (this.Horizontal ? this.X : this.Y) + this.Length)
                locs.Add(new Point(this.Horizontal ? pos++ : this.X, this.Vertical ? pos++ : this.Y));

            return locs.ToArray();
        }

        public bool CanBeMovedTo(int x, int y)
        {
            if (this.Vertical)
            {
                if (this.X != x) return false;
                if (y < 0 || y + this.Length > this.ParentGrid.Size) return false;
                for (int checkY = y; checkY < y + this.Length; checkY++)
                    foreach (Car car in this.ParentGrid.Cars)
                        if (car != this && car.ContainsLocation(x, checkY))
                            return false;
                return true;
            }
            else
            {
                if (this.Y != y) return false;
                if (x < 0 || x + this.Length > this.ParentGrid.Size) return false;
                for (int checkX = x; checkX < x + this.Length; checkX++)
                    foreach (Car car in this.ParentGrid.Cars)
                        if (car != this && car.ContainsLocation(checkX, y))
                            return false;
                return true;
            }
        }

        /// <summary>
        /// Moves this car as far as it is allowed to in its row or column
        /// (depending on its orientation), and returns the number of grid
        /// locations that it moved.
        /// </summary>
        /// <param name="newPos">
        /// The new position in the X direction or the Y direction, depending on
        /// whether the car is oriented horizontally or vertically, respectively.
        /// </param>
        /// <returns>
        /// The number of grid locations that this car moved. Up or left is
        /// negative, while down or right is positive. If this car did not move,
        /// the result is zero.
        /// </returns>
        public int MoveAsFarAsAllowed(int newPos)
        {
            int oldPos = newPos;
            // basically just move the car as far as it can without hitting another car.
            // don't just check the new spot; check every spot from old to
            // new to avoid 'jumping' other cars.
            if (this.Horizontal)
            {
                int oldX = this.X;
                while (oldX != newPos)
                {
                    if (newPos > oldX) oldX++;
                    else oldX--;
                    if (this.CanBeMovedTo(oldX, this.Y))
                        this.X = newPos;
                    else break;
                }
                return this.X - oldPos;
            }
            else
            {
                int oldY = this.Y;
                while (oldY != newPos)
                {
                    if (newPos > oldY) oldY++;
                    else oldY--;
                    if (this.CanBeMovedTo(this.X, oldY))
                        this.Y = newPos;
                    else break;
                }
                return this.Y - oldPos;
            }
        }

        public override string ToString()
        {
            return "Car:" + (Horizontal ? "H" : "V") + ",X=" + X + ",Y=" + Y + ",L=" + Length + (PlayerCar ? ",P" : "");
        }
    }
}
